#include "key.h"
#include "log.h"

#define KEY_ST_NONE        0//nothing is detected
#define KEY_ST_START       1//detect an active level, wait for shake
#define KEY_ST_DETECTED_1  2//have detected a real press
#define KEY_ST_DETECTED_2  3//have detected a lone press

typedef struct key_info
{
    int32_t    key;
    key_read_t read;
    key_cb_t   callback;
    int32_t    status;
    int32_t    active_lv;
    uint32_t   ts_shake;
#ifdef KEY_CONT_PRESSED_ENABLE
    uint32_t   ts_cont;
#endif
#ifdef KEY_LONG_PRESSED_ENABLE
    uint32_t   ts_long;
#endif
}key_info_t;

typedef struct key_evtbuf
{
    key_evt_t  buf[KEY_EVT_BUF_SIZE];
    uint32_t   size;

}key_evtbuf_t;

static key_info_t   g_key_info[KEY_MAX_NUM];
static key_evtbuf_t g_key_evtbuf;

void key_evtbuf_input(key_evt_t *event)
{
    if(g_key_evtbuf.size < KEY_EVT_BUF_SIZE)
    {
        g_key_evtbuf.buf[g_key_evtbuf.size] = *event;
        g_key_evtbuf.size++;
    }
}

int32_t key_event_get(key_evt_t *event)
{
    int32_t i;

    if(g_key_evtbuf.size == 0)
        return -1;
    
    *event = g_key_evtbuf.buf[0];
    for(i=0; i<g_key_evtbuf.size-1; i++)
    {
        g_key_evtbuf.buf[i] = g_key_evtbuf.buf[i+1];
    }
    g_key_evtbuf.size--;
    
    return 0;
}

int32_t key_event_get_num(void)
{
    return g_key_evtbuf.size;
}

void key_event_clear(void)
{
    g_key_evtbuf.size = 0;
}

void key_init(void)
{
    int32_t i;
    for(i=0; i<KEY_MAX_NUM; i++)
    {
        g_key_info[i].key       = -1;
        g_key_info[i].read      = 0;
        g_key_info[i].callback  = 0;
        g_key_info[i].status    = KEY_ST_NONE;
        g_key_info[i].active_lv = 1;
        g_key_info[i].ts_shake  = 0;
#ifdef KEY_CONT_PRESSED_ENABLE
        g_key_info[i].ts_cont   = 0;
#endif
#ifdef KEY_LONG_PRESSED_ENABLE
        g_key_info[i].ts_long   = 0;
#endif
    }
    
    g_key_evtbuf.size  = 0;
}

int32_t key_register(int32_t key, int32_t level, key_read_t read, key_cb_t cb)
{
    int32_t i;
    if(key < 0 || !read)
        return -1;

    for(i=0; i<KEY_MAX_NUM; i++)
    {
        if(g_key_info[i].key == -1)
        {
            g_key_info[i].key       = key;
            g_key_info[i].read      = read;
            g_key_info[i].callback  = cb;
            g_key_info[i].active_lv = level;
            return 0;
        }
    }
    
    return -1;
}

void key_detect(uint32_t ts)
{
    int32_t i, level, active;
    key_evt_t event;
    key_info_t *info;
    
    for(i=0; i<KEY_MAX_NUM; i++)
    {
        info = &g_key_info[i];
        if(info->key == -1 || info->read == 0)
            return;

        level = info->read(info->key);
        active = info->active_lv ? (level ? 1 : 0) : (level ? 0 : 1);
        event.id = i;
        if(info->status == KEY_ST_NONE)
        {
            //Detect an active level
            if(active)
            {
                info->status = KEY_ST_START;
                info->ts_shake = ts;
            }
        }else if(info->status == KEY_ST_START)
        {
            if(ts - info->ts_shake >= KEY_SHAKE_TIME)
            {
                if(active)
                {
                    //Detect one press
                    info->status = KEY_ST_DETECTED_1;
                    event.key = info->key;
                    event.evt = KEY_EVT_PRESSED;
                    key_evtbuf_input(&event);
#ifdef KEY_CONT_PRESSED_ENABLE           
                    info->ts_cont = ts;
#endif
#ifdef KEY_LONG_PRESSED_ENABLE
                    info->ts_long  = ts;
#endif
                }else
                {
                    //This is a shake
                    info->status = KEY_ST_NONE;
                }
            }else
            {
                //Wait for shake, do nothing.
            }
        }else if(info->status == KEY_ST_DETECTED_1 || info->status == KEY_ST_DETECTED_2)
        {
            if(active)
            {
#ifdef KEY_CONT_PRESSED_ENABLE   
                //Detect continuous press. Don't care it's KEY_ST_DETECTED_1 or KEY_ST_DETECTED_2.
                if(ts - info->ts_cont >= KEY_CONT_PRESSED_TIME)
                {
                    info->ts_cont = ts;
                    event.key = info->key;
                    event.evt = KEY_EVT_CONT_PRESSED;
                    key_evtbuf_input(&event);
                }
#endif
#ifdef KEY_LONG_PRESSED_ENABLE
                //Detect long press. Stop in KEY_ST_DETECTED_2 to make sure we just detect once.
                if((info->status == KEY_ST_DETECTED_1) && (ts - info->ts_long >= KEY_LONG_PRESSED_TIME))
                {
                    info->status = KEY_ST_DETECTED_2;
                    event.key = info->key;
                    event.evt = KEY_EVT_LONG_PRESSED;
                    key_evtbuf_input(&event);
                }
#endif
            }else
            {
                //Key is released.
                info->status = KEY_ST_NONE;
                event.key = info->key;
                event.evt = KEY_EVT_RELEASED;
                key_evtbuf_input(&event);
            }
        }
    }
}

void key_handle(void)
{
    key_evt_t event;
    while(key_event_get_num())
    {
        key_event_get(&event);
        LOGI("key_evt %d, %d\r\n", event.key, event.evt);
        if(g_key_info[event.id].callback)
            g_key_info[event.id].callback(event);
    }
}

#include "loopsys.h"
#include "console.h"

static void key_task_func(loop_task_t *self, uint32_t events)
{
    uint32_t ts = loopsys_systime_get_ms();
    key_detect(ts);
    key_handle();
    
    //loopsys_msg_publish
    loopsys_event_clear_all(self);
}

void key_task_init(void)
{
    loop_task_t *task;
    
    key_init();
    task = loopsys_task_add(5, key_task_func);
    if(task)
    {
        loopsys_task_timer_enable(task, LOOPSYS_TIMER_FOREVER, 10);
        loopsys_task_timer_start(task);
    }
}
